// Copyright (c) Lawrence Livermore National Security, LLC and other VisIt
// Project developers.  See the top-level LICENSE file for dates and other
// details.  No copyright assignment is required to contribute to VisIt.

#include <DDTSession.h>
#include <DDTManager.h>

#include <ViewerMessaging.h>
#include <ViewerMethods.h>
#include <ViewerPlotList.h>
#include <ViewerText.h>
#include <ViewerWindow.h>
#include <ViewerWindowManager.h>
#include <ViewerEngineManagerInterface.h>
#include <EngineKey.h>

#include <QLocalSocket>
#include <QTextStream>

// ****************************************************************************
// Method: DDTSession::DDTSession
//
// Purpose:
//    Constructor for the DDTSession class. Creates a socket connection
//    to DDT.
//
// Arguments:
//    server: The file path to the named socket used to extablish
//            connections to DDT
//
// Programmer: Jonathan Byrd
// Creation:   Sun Dec 18, 2011
//
// Modifications:
//
// ****************************************************************************

DDTSession::DDTSession(const QString server) : ViewerBaseUI(),
    mSocket(NULL), mErrorText(QString()), mServer(server)
{
    mSocket = new QLocalSocket(this);

    connect(mSocket,SIGNAL(stateChanged(QLocalSocket::LocalSocketState)),
            this,SIGNAL(statusChanged()));
    connect(mSocket,SIGNAL(error(QLocalSocket::LocalSocketError)),
            this, SLOT(errorHandler(QLocalSocket::LocalSocketError)));
    connect(mSocket,SIGNAL(connected()),
            this, SLOT(completeConnection()));

    mSocket->connectToServer(mServer,QIODevice::ReadWrite);
}

// ****************************************************************************
// Method: DDTSession::~DDTSession
//
// Purpose:
//    Destructor for the DDTSession class. Disconnects from DDT
//    if not already disconnected.
//
// Programmer: Jonathan Byrd
// Creation:   Sun Dec 18, 2011
//
// Modifications:
//
// ****************************************************************************

DDTSession::~DDTSession()
{
    if (mSocket)
        delete mSocket;
}

// ****************************************************************************
// Method: DDTSession::connected()
//
// Purpose:
//    Determines the socket to DDT is currently in its 'connected' state
//
// Programmer: Jonathan Byrd
// Creation:   Sun Dec 18, 2011
//
// Modifications:
//
// ****************************************************************************

bool
DDTSession::connected()
{
    return mSocket->state() == QLocalSocket::ConnectedState;
}

// ****************************************************************************
// Method: DDTSession::disconnect
//
// Purpose:
//    Disconnects the socket to DDT
//
// Programmer: Jonathan Byrd
// Creation:   Sun Dec 18, 2011
//
// Modifications:
//
// ****************************************************************************

void
DDTSession::disconnect()
{
    mSocket->disconnectFromServer();
}

// ****************************************************************************
// Method: DDTSession::disconnected
//
// Purpose:
//    Determines if the socket connection to DDT has been disconnected /
//    never was connected
//
// Programmer: Jonathan Byrd
// Creation:   Sun Dec 18, 2011
//
// Modifications:
//
// ****************************************************************************

bool
DDTSession::disconnected()
{
    return mSocket->state() == QLocalSocket::UnconnectedState;
}

// ****************************************************************************
// Method: DDTSession::completeConnection
//
// Purpose:
//    Completes a connection to DDT by identifying this socket as coming
//    from VisIt.
//
// Programmer: Jonathan Byrd
// Creation:   Sun Dec 18, 2011
//
// Modifications:
//
// ****************************************************************************

void DDTSession::completeConnection()
{
    connect(this,SIGNAL(statusChanged()),
            DDTManager::getInstance(),SIGNAL(statusChanged()));

    connect(mSocket,SIGNAL(readyRead()),this,SLOT(readDataFromDDT()));
    mSocket->readAll();  // Clear socket's buffer so signal gets send readReady next time input received

    mSocket->write("[VisIt]\n");
    mSocket->flush();

    emit statusChanged();
}

// ****************************************************************************
// Method: DDTSession::statusString
//
// Purpose:
//     Provides a human readable string summarising the state of
//     the connection to DDT
//
// Programmer: Jonathan Byrd
// Creation:   Sun Dec 18, 2011
//
// Modifications:
//
// ****************************************************************************

QString
DDTSession::statusString()
{
    if (!mErrorText.isEmpty())
        return mErrorText;

    switch(mSocket->state())
    {
    case QLocalSocket::UnconnectedState:
        return tr("Not connected");
    case QLocalSocket::ConnectingState:
        return tr("Connecting...");
    case QLocalSocket::ConnectedState:
        return tr("Connected");
    case QLocalSocket::ClosingState:
        return tr("Disconnecting...");
    default:
        return tr("Not connected");
    }
}

// ****************************************************************************
// Method: DDTSession::setFocusOnDomain
//
// Purpose:
//     Sends a command over the socket instructing DDT to focus on a
//     specific domain
//
// Programmer: Jonathan Byrd
// Creation:   Sun Dec 18, 2011
//
// Modifications:
//
// ****************************************************************************

void
DDTSession::setFocusOnDomain(const int domain)
{
    if (domain == -1)
    {
        GetViewerMessaging()->Error(
            TR("No domain information, may not be a parallel simulation. "
               "DDT's focus will not be changed."));
    }
    else if (mSocket->state() == QLocalSocket::ConnectedState)
    {
        const QString str = QString("[VisIt] focus domain %0\n").arg(QString::number(domain));
        mSocket->write(str.toLatin1().constData());
        mSocket->flush();
    }
    else
    {
        GetViewerMessaging()->Error(
           TR("Cannot focus DDT on domain %1 as VisIt is not currently "
              "connected to DDT.").
           arg(domain));
    }
}

// ****************************************************************************
// Method: DDTSession::setFocusOnElement
//
// Purpose:
//     Sends a command over the socket instructing DDT to focus on a
//     specific element within a domain
//
// Programmer: Jonathan Byrd
// Creation:   Fri Feb 1, 2013
//
// Modifications:
//
// ****************************************************************************

void
DDTSession::setFocusOnElement(const int domain, const std::string& variable,
        const int elementNumber, const std::string& value)
{
    int safeDomain;
    if (domain < 0) // No domain information. May be a 1-proc simulation
        safeDomain = 0; // DDT assumes domain == MPI rank. Negative domain nonsensical.
    else
        safeDomain = domain;

    if (elementNumber < 0)
    {
        GetViewerMessaging()->Error(
           TR("Invalid element number (%1). DDT's focus will not be changed.").
           arg(elementNumber));
    }
    else if (mSocket->state() == QLocalSocket::ConnectedState)
    {
        QString str = QString("[VisIt];;command=focus;;domain=%0;;variable=%1;;element=%2;;value=%3\n").arg(
                QString::number(safeDomain),
                QString::fromLatin1(variable.c_str()),
                QString::number(elementNumber),
                QString::fromLatin1(value.c_str()));
        mSocket->write(str.toLatin1().constData());
        mSocket->flush();
    }
    else
    {
        GetViewerMessaging()->Error(
            TR("Cannot focus DDT on element %1 domain %2 as VisIt is not "
               "currently connected to DDT.").
            arg(elementNumber).arg(domain));
    }
}

// ****************************************************************************
// Method: DDTSession::errorHandler
//
// Purpose:
//     Handles any error from the socket, creating a human-readable
//     error message
//
// Programmer: Jonathan Byrd
// Creation:   Sun Dec 18, 2011
//
// Modifications:
//
// ****************************************************************************

void
DDTSession::errorHandler(QLocalSocket::LocalSocketError err)
{
    switch(err)
    {
    case QLocalSocket::ConnectionRefusedError:
        mErrorText = tr("Connection refused"); break;
    case QLocalSocket::PeerClosedError:
        mErrorText = tr("Connection closed by DDT"); break;
    case QLocalSocket::ServerNotFoundError:
        mErrorText = tr("DDT not found"); break;
    case QLocalSocket::SocketAccessError:
        mErrorText = tr("Insufficient permissions"); break;
    case QLocalSocket::SocketResourceError:
        mErrorText = tr("Too many sockets"); break;
    case QLocalSocket::SocketTimeoutError:
        mErrorText = tr("Timed out"); break;
    case QLocalSocket::ConnectionError:
        mErrorText = tr("Connection error"); break;
    default:
        mErrorText = mSocket->errorString(); break;
    }

    emit statusChanged();
}

// ****************************************************************************
// Method: DDTSession::readDataFromDDT
//
// Purpose:
//     Handles instructions received from DDT over the socket
//
// Programmer: Jonathan Byrd
// Creation:   Sun Dec 18, 2011
//
// Modifications:
//
// ****************************************************************************

void
DDTSession::readDataFromDDT()
{
    QTextStream in(mSocket);

    QString message = in.readLine();
    while(!message.isNull())       // For each line of text received from socket
    {
        if (message=="raise")
        {
            // There could be multiple viewer windows. We don't know which one
            // corresponds to the DDT instance that sent this request, so
            // raise all of them that come from DDT-named database
            const int numWindows = ViewerWindowManager::Instance()->GetNumWindows();
            for (int i=0; i<numWindows; ++i)
            {
                ViewerWindow* win = ViewerWindowManager::Instance()->GetWindow(i);
                if (DDTManager::isDatabaseDDTSim(win->GetPlotList()->GetDatabaseName()))
                    win->ActivateWindow();
            }
        }
        else if (message=="release")
        {
            // There could be multiple viewer windows. We don't know which one
            // corresponds to the DDT instance that sent this request, so send
            // command to all of them that come from DDT-named database
            const int numWindows = ViewerWindowManager::Instance()->GetNumWindows();
            for (int i=0; i<numWindows; ++i)
            {
                ViewerWindow* win = ViewerWindowManager::Instance()->GetWindow(i);
                if (DDTManager::isDatabaseDDTSim(win->GetPlotList()->GetDatabaseName()))
                {
                    const EngineKey &key = win->GetPlotList()->GetEngineKey();
                    if (key.IsSimulation())
                    {
                        GetViewerEngineManager()->SendSimulationCommand(
                                key, "DDT", "");
                    }
                }
            }
        }
        else if (message=="exit")
        {
            GetViewerMethods()->Close();
        }
        else
        {
            GetViewerMessaging()->Error(
                TR("Unrecognised message from DDT: %1").
                arg(message.toStdString()));
        }

        message = in.readLine();
    }
}
